package hos.map.coordinate;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import hos.map.area.AreaCalculator;
import hos.map.area.AreaCalculatorInterface;
import hos.map.geometry.Geometry;
import hos.map.tool.CalculatorUtils;
import hos.map.tool.MathUtil;

/**
 * <p>Title: LatLngBounds </p>
 * <p>Description: 矩形边框 </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2023-12-07 22:23
 */
public class LatLngBounds extends Geometry implements AreaCalculatorInterface {

    private LatLng sw;
    private LatLng ne;

    private AreaCalculator mAreaCalculator;

    /**
     * 从map中获取经纬度
     */
    public static LatLngBounds fromMap(List<Map<String, Double>> list, CoordinateType type) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        return new LatLngBounds(LatLngCollection.fromMap(list, type));
    }

    /**
     * 从map中获取经纬度
     */
    public static LatLngBounds fromMapLatLng(List<Map<String, Object>> list) {
        return fromMapLatLng(list, null);
    }

    /**
     * 从map中获取经纬度
     */
    public static LatLngBounds fromMapLatLng(List<Map<String, Object>> list, CoordinateType type) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        return new LatLngBounds(LatLngCollection.fromMapLatLng(list, type));
    }

    public LatLngBounds(CoordinateType type) {
        super(type);
    }

    public LatLngBounds(LatLng sw, LatLng ne) {
        this(sw, ne, sw.getType());
    }

    public LatLngBounds(LatLng sw, LatLng ne, CoordinateType type) {
        super(type);
        this.sw = sw;
        this.ne = ne;
    }

    public LatLngBounds(List<LatLng> collection) {
        this(collection, parseType(collection, null));
    }

    public LatLngBounds(List<LatLng> collection, CoordinateType type) {
        super(parseType(collection, type));
        if (collection == null) {
            return;
        }
        double minX = 180;
        double maxX = -180;
        double minY = 90;
        double maxY = -90;
        for (LatLng point : collection) {
            minX = Math.min(minX, point.getLongitude());
            minY = Math.min(minY, point.getLatitude());
            maxX = Math.max(maxX, point.getLongitude());
            maxY = Math.max(maxY, point.getLatitude());
        }
        sw = new LatLng(minY, minX, type);
        ne = new LatLng(maxY, maxX, type);
    }

    private static CoordinateType parseType(List<LatLng> collection, CoordinateType type) {
        if (type != null) {
            return type;
        }
        if (collection == null || collection.isEmpty()) {
            return CoordinateType.wgs84;
        }
        for (LatLng point : collection) {
            if (point != null) {
                return point.getType();
            }
        }
        return CoordinateType.wgs84;
    }

    @Override
    public LatLngBounds toBaiDu() {
        return new LatLngBounds(sw.toBaiDu(), ne.toBaiDu(), CoordinateType.bd09mc);
    }

    @Override
    public LatLngBounds toAMap() {
        return new LatLngBounds(sw.toAMap(), ne.toAMap(), CoordinateType.gcj02);
    }

    @Override
    public LatLngBounds toWgs() {
        return new LatLngBounds(sw.toWgs(), ne.toWgs(), CoordinateType.wgs84);
    }

    /**
     * 以[latLng]坐标点扩展边界框。这个方法改变调用它的边界对象。
     */
    public LatLngBounds extend(LatLng latLng) {
        return _extend(latLng, latLng);
    }

    /**
     * 用其他[bounds]对象展开边界框。如果提供的[bounds]对象小于当前对象，则不会收缩。此方法会改变调用它的边界对象。
     */
    public LatLngBounds extendBounds(LatLngBounds bounds) {
        return _extend(bounds.sw, bounds.ne);
    }

    private LatLngBounds _extend(LatLng sw2, LatLng ne2) {
        if (sw == null) {
            sw = sw2;
        }
        if (ne == null) {
            ne = ne2;
        }
        sw = new LatLng(
                Math.min(sw2.getLatitude(), sw.getLatitude()),
                Math.min(sw2.getLongitude(), sw.getLongitude()), getType()
        );
        ne = new LatLng(
                Math.max(ne2.getLatitude(), ne.getLatitude()),
                Math.max(ne2.getLongitude(), ne.getLongitude()), getType()
        );
        return this;
    }

    /**
     * 获得边界西南角的坐标
     */
    public LatLng getSouthWest() {
        return sw;
    }

    /**
     * 获得边界东北角的坐标
     */
    public LatLng getNorthEast() {
        return ne;
    }

    /**
     * 获得边界的西部边缘
     */
    public double getWest() {
        return getSouthWest().getLongitude();
    }

    /**
     * 获得边界的南缘
     */
    public double getSouth() {
        return getSouthWest().getLatitude();
    }

    /**
     * 获得边界的东部边缘
     */
    public double getEast() {
        return getNorthEast().getLongitude();
    }

    /**
     * 获得边界的北部边缘
     */
    public double getNorth() {
        return getNorthEast().getLatitude();
    }

    /**
     * 获得边界西北角的坐标
     */
    public LatLng getNorthWest() {
        return new LatLng(getNorth(), getWest(), getType());
    }

    /**
     * 获得边界东南角的坐标
     */
    public LatLng getSouthEast() {
        return new LatLng(getSouth(), getEast(), getType());
    }


    public List<LatLng> getData() {
        List<LatLng> list = new ArrayList<>();
        // 西北
        list.add(getNorthWest());
        // 东北
        list.add(getNorthEast());
        // 东南
        list.add(getSouthEast());
        // 西南
        list.add(getSouthWest());
        return list;
    }

    /**
     * 检查[point]是否在边界内
     */
    public boolean contains(LatLng point) {
        return containsBounds(new LatLngBounds(point, point, getType()));
    }

    /**
     * 检查[bounds]是否包含在边界内
     */
    public boolean containsBounds(LatLngBounds bounds) {
        final LatLng sw2 = bounds.sw;
        final LatLng ne2 = bounds.ne;
        return (sw2.getLatitude() >= sw.getLatitude()) &&
                (ne2.getLatitude() <= ne.getLatitude()) &&
                (sw2.getLongitude() >= sw.getLongitude()) &&
                (ne2.getLongitude() <= ne.getLongitude());
    }

    /**
     * 检查边界的至少一条边是否与其他边界重叠
     */
    public boolean isOverlapping(LatLngBounds bounds) {
        if (sw.getLatitude() > bounds.ne.getLatitude() ||
                ne.getLatitude() < bounds.sw.getLatitude() ||
                ne.getLongitude() < bounds.sw.getLongitude() ||
                sw.getLongitude() > bounds.ne.getLongitude()) {
            return false;
        }
        return true;
    }

    @Override
    public boolean equals(Object other) {
        if (other == null) {
            return false;
        }
        if (other instanceof LatLngBounds) {
            LatLngBounds bounds = (LatLngBounds) other;
            return bounds.ne != null && bounds.sw != null && bounds.ne.equals(ne) && bounds.sw.equals(sw);
        }
        return super.equals(other);
    }

    /**
     * 获得边界中心的坐标
     */
    public LatLng getCenter() {
        final double phi1 = getSouthWest().getLatitudeInRad();
        final double lambda1 = getSouthWest().getLongitudeInRad();
        final double phi2 = getNorthEast().getLatitudeInRad();

        final double dLambda = MathUtil.degToRadian(
                (getNorthEast().getLongitude() - getSouthWest().getLongitude()));
        // delta lambda = lambda2-lambda1

        final double bx = Math.cos(phi2) * Math.cos(dLambda);
        final double by = Math.cos(phi2) * Math.sin(dLambda);
        final double phi3 = Math.atan2(Math.sin(phi1) + Math.sin(phi2),
                Math.sqrt((Math.cos(phi1) + bx) * (Math.cos(phi1) + bx) + by * by));
        final double lambda3 = lambda1 + Math.atan2(by, Math.cos(phi1) + bx);

        // phi3 and lambda3 are actually in radians and LatLng wants degrees
        return new LatLng(MathUtil.toDegrees(phi3), MathUtil.toDegrees(lambda3), getType());
    }

    /**
     * 设置面积计算监听
     */
    public void setAreaCalculator(AreaCalculator mAreaCalculator) {
        this.mAreaCalculator = mAreaCalculator;
    }

    /**
     * 获取矩形的面积
     */
    @Override
    public double area() {
        List<LatLng> list = getData();
        // 西北
        list.add(getNorthWest());
        if (mAreaCalculator != null) {
            return mAreaCalculator.area(list);
        }
        return CalculatorUtils.area(list);
    }

    @Override
    public Map<String, Object> toMap() {
        Map<String, Object> data = super.toMap();
        data.put("sw", sw.toMap());
        data.put("ne", ne.toMap());
        return data;
    }

    @Override
    public String toJson() {
        Map<String, Object> data = toMap();
        Set<String> keySet = data.keySet();
        StringBuilder builder = new StringBuilder();
        builder.append("{");
        for (String key : keySet) {
            if (builder.length() > 1) {
                builder.append(",");
            }
            builder.append("\"").append(key)
                    .append("\"").append(":");
            if (key.equals("sw")) {
                String jsonData = sw.toJson();
                builder.append(jsonData);
            } else if (key.equals("ne")) {
                String jsonData = ne.toJson();
                builder.append(jsonData);
            } else {
                Object value = data.get(key);
                if (value instanceof String) {
                    builder.append("\"")
                            .append(value)
                            .append("\"");
                } else {
                    builder.append(value);
                }
            }
        }
        builder.append("}");
        return builder.toString();
    }
}
